import docker
from docker.errors import DockerException
import time
import logging
import os
import platform
import math

logger = logging.getLogger(__name__)

class DockerUtils:
    def __init__(self, timeout=None):
        # 如果没有指定超时时间，尝试从配置获取，否则使用默认值
        if timeout is None:
            try:
                from config import config
                self.timeout = config.DOCKER_TIMEOUT
            except ImportError:
                self.timeout = 300  # 默认5分钟
        else:
            self.timeout = timeout
            
        self.client = None
        self.last_check_time = 0
        self.connection_status = None
        
        # 初始化传输优化器
        try:
            from utils.transfer_optimizer import TransferOptimizer
            self.transfer_optimizer = TransferOptimizer()
            logger.info("传输优化器初始化成功")
        except ImportError:
            self.transfer_optimizer = None
            logger.warning("传输优化器初始化失败，将使用默认传输方式")
        
        logger.info(f"Docker工具类初始化，超时时间: {self.timeout}秒")
        self.initialize_client()

    def initialize_client(self):
        """初始化Docker客户端并验证连接
        使用from_env()自动检测环境变量中的Docker配置，兼容性更好
        """
        try:
            # 检测操作系统，选择合适的Docker连接方式
            if platform.system() == "Windows":
                # Windows下尝试使用TCP连接或命名管道
                try:
                    # 尝试使用默认的Docker Desktop连接，增加超时时间
                    self.client = docker.from_env(timeout=self.timeout)
                except:
                    # 如果失败，尝试使用TCP连接（需要Docker Desktop开启TCP端口）
                    self.client = docker.DockerClient(
                        base_url="tcp://localhost:2375",
                        timeout=self.timeout
                    )
            else:
                # Linux/macOS使用Unix socket，增加超时时间
                self.client = docker.DockerClient(
                    base_url="unix://var/run/docker.sock",
                    timeout=self.timeout
                )
                
        except DockerException as e:
            raise Exception(f"初始化Docker客户端失败: {str(e)}")

    def check_docker_connection(self, force_check=False):
        """检查Docker连接状态，包含详细错误信息"""
        current_time = time.time()
        if not force_check and self.connection_status is not None and (current_time - self.last_check_time) < 5:
            return self.connection_status

        if not self.client:
            self.connection_status = (False, "Docker客户端未初始化")
            return self.connection_status

        try:
            self.client.ping()
            self.connection_status = (True, "成功连接到本地Docker服务")
        except PermissionError:
            self.connection_status = (False, (
                "权限不足：无法访问/var/run/docker.sock。"
                "请将用户加入docker组并重启：sudo usermod -aG docker $USER"
            ))
        except FileNotFoundError:
            self.connection_status = (False, (
                "未找到/var/run/docker.sock文件。"
                "请检查Docker是否已安装并启动：sudo systemctl start docker"
            ))
        except DockerException as e:
            self.connection_status = (False, f"Docker连接失败: {str(e)}")
        except Exception as e:
            self.connection_status = (False, f"检查连接时发生错误: {str(e)}")
        
        self.last_check_time = current_time
        return self.connection_status

    def get_docker_version(self):
        try:
            if not self.client:
                self.initialize_client()
            return True, self.client.version()
        except Exception as e:
            return False, f"获取版本信息失败: {str(e)}"

    def list_containers(self, all=False):
        try:
            if not self.client:
                self.initialize_client()
            return True, self.client.containers.list(all=all)
        except Exception as e:
            return False, f"列出容器失败: {str(e)}"

    def pull_image(self, image_name):
        """拉取Docker镜像"""
        try:
            if not self.client:
                self.initialize_client()
            
            # 获取拉取超时时间（用于客户端连接超时）
            try:
                from config import config
                pull_timeout = config.DOCKER_PULL_TIMEOUT
            except ImportError:
                pull_timeout = 600  # 默认10分钟
            
            logger.info(f"开始拉取镜像: {image_name}，连接超时时间: {pull_timeout}秒")
            
            # 注意：Docker Python SDK的pull方法不支持timeout参数
            # 超时控制通过客户端初始化时的timeout参数实现
            image = self.client.images.pull(image_name)
            logger.info(f"镜像拉取成功: {image_name}")
            return image, None
            
        except Exception as e:
            error_msg = f"拉取镜像失败: {str(e)}"
            logger.error(error_msg)
            return None, error_msg

    def save_image(self, image_name, tar_path):
        """保存Docker镜像为tar文件"""
        try:
            if not self.client:
                self.initialize_client()
            
            logger.info(f"开始保存镜像: {image_name} -> {tar_path}")
            
            # 获取镜像对象
            image = self.client.images.get(image_name)
            
            # 获取镜像大小信息
            image_size = image.attrs.get('Size', 0)
            logger.info(f"镜像大小: {self.format_size(image_size)}")
            
            # 获取保存超时时间
            try:
                from config import config
                save_timeout = config.DOCKER_SAVE_TIMEOUT
                chunk_size = config.TRANSFER_CHUNK_SIZE
            except ImportError:
                save_timeout = 900  # 默认15分钟
                chunk_size = 1024 * 1024  # 默认1MB
            
            # 根据镜像大小动态调整超时时间
            # 基础超时时间：配置的保存超时时间
            # 每100MB增加30秒超时时间
            dynamic_timeout = max(save_timeout, int(image_size / (100 * 1024 * 1024)) * 30 + save_timeout)
            logger.info(f"使用动态超时时间: {dynamic_timeout}秒")
            
            # 保存镜像到tar文件，使用配置的块大小
            # 注意：image.save()方法本身不支持timeout参数，超时控制通过客户端配置实现
            with open(tar_path, 'wb') as f:
                for chunk in image.save(chunk_size=chunk_size):
                    f.write(chunk)
            
            # 验证保存的文件
            if os.path.exists(tar_path):
                file_size = os.path.getsize(tar_path)
                logger.info(f"镜像保存成功: {tar_path}")
                logger.info(f"保存的文件大小: {self.format_size(file_size)}")
                return tar_path, None
            else:
                raise Exception("保存的文件不存在")
            
        except Exception as e:
            error_msg = f"保存镜像失败: {str(e)}"
            logger.error(error_msg)
            return None, error_msg

    def format_size(self, size_bytes):
        """格式化文件大小显示"""
        if size_bytes == 0:
            return "0 B"
        size_names = ["B", "KB", "MB", "GB", "TB"]
        i = int(math.floor(math.log(size_bytes, 1024)))
        p = math.pow(1024, i)
        s = round(size_bytes / p, 2)
        return f"{s} {size_names[i]}"
